﻿Imports System.Globalization
Imports System.Security.Principal

<HandleError()> _
<OutputCache(Location:=OutputCacheLocation.None)> _
Public Class AccountController
    Inherits System.Web.Mvc.Controller

    Private _formsAuth As IFormsAuthentication
    Private _provider As MembershipProvider

    ' This constructor is used by the MVC framework to instantiate the controller using
    ' the default forms authentication and membership providers.

    Sub New()
        Me.New(Nothing, Nothing)
    End Sub

    ' This constructor is not used by the MVC framework but is instead provided for ease
    ' of unit testing this type. See the comments at the end of this file for more
    ' information.

    Sub New(ByVal formsAuth As IFormsAuthentication, ByVal provider As MembershipProvider)
        _formsAuth = If(formsAuth, New FormsAuthenticationWrapper())
        _provider = If(provider, Membership.Provider)
    End Sub

    ReadOnly Property FormsAuth() As IFormsAuthentication
        Get
            Return _formsAuth
        End Get
    End Property

    ReadOnly Property Provider() As MembershipProvider
        Get
            Return _provider
        End Get
    End Property

    <Authorize()> _
    Function ChangePassword()

        ViewData("Title") = "Change Password"
        ViewData("PasswordLength") = Provider.MinRequiredPasswordLength

        Return View()
    End Function

    <Authorize()> _
    <AcceptVerbs(HttpVerbs.Post)> _
    Function ChangePassword(ByVal currentPassword As String, ByVal newPassword As String, ByVal confirmPassword As String)

        ViewData("Title") = "Change Password"
        ViewData("PasswordLength") = Provider.MinRequiredPasswordLength

        ' Basic parameter validation
        If String.IsNullOrEmpty(currentPassword) Then
            ModelState.AddModelError("currentPassword", "You must specify a current password.")
        End If
        If newPassword Is Nothing OrElse newPassword.Length < Provider.MinRequiredPasswordLength Then
            ModelState.AddModelError("newPassword", String.Format(CultureInfo.CurrentCulture, _
                                                   "You must specify a new password of {0} or more characters.", _
                                                   Provider.MinRequiredPasswordLength))
        End If
        If Not String.Equals(newPassword, confirmPassword, StringComparison.Ordinal) Then
            ModelState.AddModelError("_FORM", "The new password and confirmation password do not match.")
        End If

        If ViewData.ModelState.IsValid Then

            ' Attempt to change password
            Dim currentUser As MembershipUser = Provider.GetUser(User.Identity.Name, True)
            Dim changeSuccessful As Boolean = False
            Try
                changeSuccessful = currentUser.ChangePassword(currentPassword, newPassword)
            Catch
                ' An exception is thrown if the new password does not meet the provider's requirements
            End Try

            If changeSuccessful Then
                Return RedirectToAction("ChangePasswordSuccess")
            Else
                ModelState.AddModelError("_FORM", "The current password is incorrect or the new password is invalid.")
            End If
        End If

        ' If we got this far, something failed, redisplay form
        Return View()
    End Function

    Function ChangePasswordSuccess()

        ViewData("Title") = "Change Password"

        Return View()
    End Function

    Function Login()

        ViewData("Title") = "Login"

        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Login(ByVal username As String, ByVal password As String, ByVal rememberMe As Boolean, ByVal returnUrl As String)

        ViewData("Title") = "Login"

        ' Basic parameter validation
        If String.IsNullOrEmpty(username) Then
            ModelState.AddModelError("username", "You must specify a username.")
        End If
        If String.IsNullOrEmpty(password) Then
            ModelState.AddModelError("password", "You must specify a password.")
        End If

        If ViewData.ModelState.IsValid Then

            ' Attempt to login
            Dim loginSuccessful As Boolean = Provider.ValidateUser(username, password)
            If loginSuccessful Then

                FormsAuth.SetAuthCookie(username, rememberMe)
                If Not String.IsNullOrEmpty(returnUrl) Then
                    Return Redirect(returnUrl)
                Else
                    Return RedirectToAction("Index", "Home")
                End If
            Else
                ModelState.AddModelError("_FORM", "The username or password provided is incorrect.")
            End If
        End If

        ' If we got this far, something failed, redisplay form
        ViewData("rememberMe") = rememberMe
        Return View()
    End Function

    Function Logout()

        FormsAuth.SignOut()

        Return RedirectToAction("Index", "Home")
    End Function

    Protected Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
        If TypeOf filterContext.HttpContext.User.Identity Is WindowsIdentity Then
            Throw New InvalidOperationException("Windows authentication is not supported.")
        End If
    End Sub

    Function Register()

        ViewData("Title") = "Register"
        ViewData("PasswordLength") = Provider.MinRequiredPasswordLength

        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Register(ByVal username As String, ByVal email As String, ByVal password As String, ByVal confirmPassword As String)

        ViewData("Title") = "Register"
        ViewData("PasswordLength") = Provider.MinRequiredPasswordLength

        ' basic parameter validation
        If String.IsNullOrEmpty(username) Then
            ModelState.AddModelError("username", "You must specify a username.")
        End If
        If String.IsNullOrEmpty(email) Then
            ModelState.AddModelError("email", "You must specify an email address.")
        End If
        If password Is Nothing OrElse password.Length < Provider.MinRequiredPasswordLength Then
            ModelState.AddModelError("password", String.Format(CultureInfo.CurrentCulture, _
                                     "You must specify a password of {0} or more characters.", _
                                     Provider.MinRequiredPasswordLength))
        End If
        If Not String.Equals(password, confirmPassword, StringComparison.Ordinal) Then
            ModelState.AddModelError("_FORM", "The new password and confirmation password do not match.")
        End If

        If ViewData.ModelState.IsValid Then

            ' Attempt to register the user
            Dim createStatus As MembershipCreateStatus
            Dim newUser As MembershipUser = Provider.CreateUser(username, password, email, Nothing, Nothing, True, Nothing, createStatus)
            If newUser IsNot Nothing Then

                FormsAuth.SetAuthCookie(username, False)
                Return RedirectToAction("Index", "Home")
            Else
                ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus))
            End If
        End If

        ' If we got this far, something failed, redisplay form
        ViewData("username") = username
        ViewData("email") = email
        Return View()
    End Function

    Private Shared Function ErrorCodeToString(ByVal createStatus As MembershipCreateStatus) As String
        ' See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
        ' a full list of status codes.
        Select Case createStatus
            Case MembershipCreateStatus.DuplicateUserName
                Return "Username already exists. Please enter a different user name."

            Case MembershipCreateStatus.DuplicateEmail
                Return "A username for that e-mail address already exists. Please enter a different e-mail address."

            Case MembershipCreateStatus.InvalidPassword
                Return "The password provided is invalid. Please enter a valid password value."

            Case MembershipCreateStatus.InvalidEmail
                Return "The e-mail address provided is invalid. Please check the value and try again."

            Case MembershipCreateStatus.InvalidAnswer
                Return "The password retrieval answer provided is invalid. Please check the value and try again."

            Case MembershipCreateStatus.InvalidQuestion
                Return "The password retrieval question provided is invalid. Please check the value and try again."

            Case MembershipCreateStatus.InvalidUserName
                Return "The user name provided is invalid. Please check the value and try again."

            Case MembershipCreateStatus.ProviderError
                Return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator."

            Case MembershipCreateStatus.UserRejected
                Return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator."

            Case Else
                Return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator."
        End Select

    End Function

End Class

' The FormsAuthentication type is sealed and contains static members, so it is difficult to
' unit test code that calls its members. The interface and helper class below demonstrate
' how to create an abstract wrapper around such a type in order to make the AccountController
' code unit testable.

Public Interface IFormsAuthentication
    Sub SetAuthCookie(ByVal userName As String, ByVal createPersistentCookie As Boolean)
    Sub SignOut()
End Interface

Public Class FormsAuthenticationWrapper
    Implements IFormsAuthentication

    Public Sub SetAuthCookie(ByVal userName As String, ByVal createPersistentCookie As Boolean) Implements IFormsAuthentication.SetAuthCookie
        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie)
    End Sub

    Public Sub SignOut() Implements IFormsAuthentication.SignOut
        FormsAuthentication.SignOut()
    End Sub
End Class
